package com.ddsso.auth.web.session.impl;

import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Random;

import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;

import com.ddsso.auth.web.exception.DistributedSessionException;
import com.ddsso.auth.web.repository.IDistributedSessionDao;
import com.ddsso.auth.web.session.DistributedSession;
import com.ddsso.auth.web.session.DistributedSessionManager;
import com.ddsso.auth.web.session.MutableHttpServletRequest;
import com.ddsso.auth.web.util.AuthDesCoderUtil;

/**
 * @author zdd
 * @package com.ddsso.auth.web.session.impl
 * @Time Created by zdd on 2016/11/2.
 * @description To-do
 */
public class DefaultDistributedSessionManager implements DistributedSessionManager, InitializingBean {

    private static final Logger logger = LoggerFactory.getLogger(DefaultDistributedSessionManager.class);

    private ServletContext servletContext;

    /** 会话域，可为<code>NULL</code>，<code>NULL</code>表示在当前域 */
    private String sessionDomain;

    /**
     * 会话cookie名称
     * <p>
     * 不能为空，默认<code>SHARE_JSESSIONID</code> ，不能为J2EE规范中的'JSESSIONID'
     */
    private String sessionCookieName = "SHARE_JSESSIONID";

    /**
     * 是否每次提交
     * <p>
     * <code>TRUE</code>表示每次提交。若是每次提交，不管会话是否有变化，均会被提交
     */
    private boolean alwaysCommit = false;

    private IDistributedSessionDao distributedSessionDao;

    private Random random = new Random();

    /** 会话超时时间，单位为秒，默认1800秒 */
    private int sessionTimeout = 1800;

    /**
     * 临时存储sessionId的token字段
     */
    private String ihomeToken = "ihome-token";

    /**
     *  @description  强制设置是否保存session到缓存，
     *  如果为false将判断session的存储属性是否sessionStoreAttribute是否有内容，有内容才进行缓存的存储
     */
    private boolean forceSaveCach=true;
    /**
     * @description 此属性主要根据业务系统登入信息存储在session属性来确定，要配置一样
     */
    private String  sessionStoreAttribute="sessionUser,VALIDATE_CODE";



    public void setForceSaveCach(boolean forceSaveCach) {
        this.forceSaveCach = forceSaveCach;
    }

    public void setSessionStoreAttribute(String sessionStoreAttribute) {
        this.sessionStoreAttribute = sessionStoreAttribute;
    }

    /**
     * (non-Javadoc)
     *
     * @see org.springframework.web.context.ServletContextAware#setServletContext(javax.servlet.ServletContext)
     */
    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    /**
     * (non-Javadoc)
     *
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        if (sessionTimeout <= 0) {
            throw new DistributedSessionException("distributed session timeout would great than zero, sessionTimeout = " + sessionTimeout);
        }
        if (StringUtils.isBlank(sessionCookieName)) {
            throw new IllegalArgumentException("sessionCookieName would not be blank!");
        }
        if (StringUtils.equals("JSESSIONID", sessionCookieName)) {
            throw new IllegalArgumentException("sessionCookieName would not equals 'JSESSIONID'!");
        }
    }

    /**
     * (non-Javadoc)
     *
     */
    @Override
    public DistributedSession getSession(String sessionId, HttpServletRequest request, HttpServletResponse response) {
        if (StringUtils.isBlank(sessionId)) {
            return null;
        }

        DistributedSession distributedSession = distributedSessionDao.getSession(sessionId);
        if (null == distributedSession) {
            logger.info("distributed session is null sessionIdMD5", DigestUtils.md5Hex(sessionId));
            final String shareSessionId=getSessionId(request,response);

            if(StringUtils.isNotBlank(shareSessionId)&& sessionId.equals(shareSessionId)){
                Cookie cookie = new Cookie(sessionCookieName, null);
                cookie.setMaxAge(0);
                response.addCookie(cookie);
                MutableHttpServletRequest mutableRequest = new MutableHttpServletRequest(request);
                mutableRequest.putHeader(ihomeToken, "");
            }
            return null;
        }

        initSession(request, response, servletContext, distributedSession, sessionTimeout);
        return distributedSession;
    }


    /**
     * (non-Javadoc)
     *
     *      javax.servlet.http.HttpServletResponse)
     */
    @Override
    public DistributedSession createSession(HttpServletRequest request, HttpServletResponse response) {
        String sessionId = createSessionId(request);
        logger.info("distributed session create sessionIdMD5 zzz {}", DigestUtils.md5Hex(sessionId));
        DistributedSession distributedSession = new DistributedSession(sessionId);
        distributedSession.setCreationTime(System.currentTimeMillis());
        distributedSession.setLastAccessedTime(System.currentTimeMillis());
        distributedSession.setLastCommittedTime(System.currentTimeMillis());
        distributedSession.setNew(true);

        initSession(request, response, servletContext, distributedSession, sessionTimeout);

        if(isForceStoreCachWhenCreateSession(distributedSession)) {
            distributedSessionDao.createSession(distributedSession);
            /**
             * @description 建立session后需要对cookie进行判断，具体步骤如下：
             *  1.查看此COOKIE是否有对应的SHARE_SESSION值；
             *  2.如果有不做处理，没有就写入当前的session的id的值
             * @author zdd
             */
            // 置为会话生命周期
            storeSessionId(response, sessionId, -1);
            response.setHeader(this.ihomeToken, AuthDesCoderUtil.desEncryptTokenOrSessionId(sessionId));
            request.setAttribute(paramSessionToken,AuthDesCoderUtil.desEncryptTokenOrSessionId(sessionId));
            MutableHttpServletRequest mutableRequest = new MutableHttpServletRequest(request);
            mutableRequest.putHeader(this.ihomeToken, AuthDesCoderUtil.desEncryptTokenOrSessionId(sessionId));
        }


        return distributedSession;
    }

    /**
     * @description 判断在创建session的时候是否强制存储session
     * @param distributedSession
     * @return
     */
    private boolean isForceStoreCachWhenCreateSession(DistributedSession distributedSession){

        if(distributedSession==null){
            return false;
        }
        if(forceSaveCach){
            return true;
        }
        if(forceSaveCach==false&&distributedSession!=null&&StringUtils.isNoneBlank(sessionStoreAttribute)){

            String [] atts=StringUtils.split(sessionStoreAttribute, ",");
            for(String att:atts){
                Object sessionStoreObj=distributedSession.getAttribute(att);
                if(sessionStoreObj!=null){
                    return true;
                }
            }
            return false;

        }

        return forceSaveCach;
    }

    /**
     * @description 内部实现加密解密处理，获取处理过程为明文
     * @param request
     *            HTTP请求
     * @param response
     *            HTTP响应
     * @return
     */
    @Override
    public String getSessionId(HttpServletRequest request, HttpServletResponse response) {
        // 优先取临时token的值作为sessionId
        if (StringUtils.isNotBlank(request.getHeader(ihomeToken))) {
            return request.getHeader(ihomeToken)!=null&&StringUtils.isNotBlank(request.getHeader(ihomeToken))?AuthDesCoderUtil.desDecryptTokenOrSessionId(request.getHeader(ihomeToken)):null;
        }
        Cookie[] cookies = request.getCookies();
        if (null != cookies) {
            for (Cookie cookie : cookies) {
                if (null != cookie) {
                    String cookieName = cookie.getName();
                    if (StringUtils.equals(cookieName, sessionCookieName)) {
                        if(cookie.getValue()!=null&&StringUtils.isNotBlank(cookie.getValue())){
                            try{
                                return AuthDesCoderUtil.desDecryptTokenOrSessionId(URLDecoder.decode(cookie.getValue(),"UTF-8"));
                            }catch (Exception e){
                                logger.info("cookie 取值出错....");
                                return "";
                            }
                        }else{
                            return "";
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * (non-Javadoc)
     *
     */
    @Override
    public void commitSession(DistributedSession distributedSession) {
        if (isNeedCommit(distributedSession)) {
            distributedSession.setLastAccessedTime(System.currentTimeMillis());
            distributedSession.setLastCommittedTime(System.currentTimeMillis());
            if(isForceStoreCachWhenCreateSession(distributedSession)){
                distributedSessionDao.updateSession(distributedSession);
            }
            //distributedSessionDao.updateSession(distributedSession);
        } else {
            logger.debug("distributed session does not need commit");
        }
        distributedSession.setCommitted(true);
    }

    /**
     * (non-Javadoc)
     *
     *      (com.ihome.framework.web.session.DistributedSession,
     *      javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse)
     */
    @Override
    public void invalidateSession(DistributedSession distributedSession, HttpServletRequest request, HttpServletResponse response) {
        distributedSessionDao.removeSession(distributedSession);
        logger.info("distributed session invalidate sessionIdMD5", DigestUtils.md5Hex(distributedSession.getId()));

        /**
         * @description 清除sessionid
         * 1.头部和值都清楚调
         * @author zdd
         */
        response.setHeader(this.ihomeToken, distributedSession.getId());
        request.setAttribute(paramSessionToken, distributedSession.getId());
        MutableHttpServletRequest mutableRequest = new MutableHttpServletRequest(request);
        mutableRequest.putHeader(this.ihomeToken, distributedSession.getId());
        storeSessionId(response, distributedSession.getId(), 0);

    }

    /**
     * 创建会话ID
     * <p>
     * 根据运行时的内存、随机数等确定，但不保证在存储端不存在
     *
     * @param request
     *            HTTP请求
     * @return 会话ID
     */
    protected String createSessionId(HttpServletRequest request) {
        StringBuffer buffer = new StringBuffer();
        long r = hashCode() ^ Runtime.getRuntime().freeMemory() ^ random.nextLong() ^ (((long) request.hashCode()) << 32);

        if (request.getRemoteAddr() != null) {
            r = r ^ request.getRemoteAddr().hashCode();
        }
        if (r < 0) {
            r = -r;
        }

        buffer.append(Long.toString(r, 36));
        buffer.append('!');
        buffer.append(System.currentTimeMillis());

        return buffer.toString();
    }

    protected void initSession(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, DistributedSession distributedSession,
                               int sessionTimeout) {
        distributedSession.setServletContext(servletContext);
        distributedSession.setRequest(request);
        distributedSession.setResponse(response);
        distributedSession.setDistributedSessionManager(this);
        distributedSession.initOriginalHashCode();
        distributedSession.initMaxInactiveInterval(sessionTimeout);
    }

    /**
     * 保存会话ID
     * <p>
     * 会话域存在则设置会话域
     *
     * @param response
     *            HTTP响应
     * @param sessionId
     *            会话ID
     * @param expiry
     *            会话有效期
     */
    protected void storeSessionId(HttpServletResponse response, String sessionId, int expiry) {
        try{

            Cookie cookie = new Cookie(sessionCookieName, URLEncoder.encode(AuthDesCoderUtil.desEncryptTokenOrSessionId(sessionId),"UTF-8"));
            cookie.setPath("/");

            if (StringUtils.isNotBlank(sessionDomain)) {
                cookie.setDomain(sessionDomain);
            }

            System.out.print(cookie.getDomain() + ":" + cookie.getValue());

            cookie.setMaxAge(expiry);
            response.addCookie(cookie);
        }catch (Exception e){
            logger.info("保存cookie出错....");
        }
    }

    protected boolean isNeedCommit(DistributedSession distributedSession) {
        // 被修改的会话必须提交
        if (distributedSession.isModified()) {
            return true;
        }

        // 上次提交时间超过会话超时的一半时，必须提交
        long elapsed = (System.currentTimeMillis() - distributedSession.getLastCommittedTime()) / 1000;
        if (elapsed > (distributedSession.getMaxInactiveInterval() / 2)) {
            return true;
        }

        // 是否每次提交
        return alwaysCommit;
    }

    public void setSessionDomain(String sessionDomain) {
        this.sessionDomain = sessionDomain;
    }

    public void setSessionCookieName(String sessionCookieName) {
        this.sessionCookieName = sessionCookieName;
    }

    public void setAlwaysCommit(boolean alwaysCommit) {
        this.alwaysCommit = alwaysCommit;
    }

    public void setDistributedSessionDao(IDistributedSessionDao distributedSessionDao) {
        this.distributedSessionDao = distributedSessionDao;
    }

    public void setSessionTimeout(int sessionTimeout) {
        this.sessionTimeout = sessionTimeout;
    }

    public void setIhomeToken(String ihomeToken) {
        this.ihomeToken = ihomeToken;
    }

    private String paramSessionToken="token";


}
